﻿// Copyright (c) 2008, Colin Burn
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 
// * Neither the name of WTCL nor the names of its contributors may be
// used to endorse or promote products derived from this software
// without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
// DAMAGE.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

using WTCL.Charts.Axis.Data;
using WTCL.Charts.Axis.Scale;
using WTCL.Charts.Axis.Ticks;
using WTCL.Charts.Axis.Interface;
using WTCL.Data.Series;
using WTCL.Data.Series.Interface;
using WTCL.Data.Type;

namespace WTCL.Charts
{
    public abstract class ValueAxisCanvas<TXType, TXSpanType, TYType, TYSpanType, TSeries, TData, TChart, TChartCanvas>
        : BaseChartCanvas<TSeries, TData, TChart, TChartCanvas>,
          IXAxisTickMarks<AxisScale<TChart, ValueAxisData<TXType, TXSpanType>, XAxisTick>>,
          IYAxisTickMarks<AxisScale<TChart, ValueAxisData<TYType, TYSpanType>, YAxisTick>>
        where TSeries : SeriesBase<TData>, ISampleXAxisSeries<IDataType<TXType, TXSpanType>>, ISampleYAxisSeries<IDataType<TYType, TYSpanType>>
        where TChart : Chart<TSeries, TData, TChart, TChartCanvas>
        where TChartCanvas : BaseChartCanvas<TSeries, TData, TChart, TChartCanvas>
    {
        private event EventHandler xInvalidate;
        public event EventHandler XInvalidate
        {
            add
            {
                xInvalidate += value;
            }
            remove
            {
                xInvalidate -= value;
            }
        }

        private event EventHandler yInvalidate;
        public event EventHandler YInvalidate
        {
            add
            {
                yInvalidate += value;
            }
            remove
            {
                yInvalidate -= value;
            }
        }

        private AxisScale<TChart, ValueAxisData<TXType, TXSpanType>, XAxisTick> xAxisScale;
        public AxisScale<TChart, ValueAxisData<TXType, TXSpanType>, XAxisTick> XAxisScale { get { return this.xAxisScale; } }

        private AxisScale<TChart, ValueAxisData<TYType, TYSpanType>, YAxisTick> yAxisScale;
        public AxisScale<TChart, ValueAxisData<TYType, TYSpanType>, YAxisTick> YAxisScale { get { return this.yAxisScale; } }

        protected ValueAxisCanvas()
        {
        }

        internal override void Init(TChart chart)
        {
            this.xAxisScale = CreateXScale(chart);
            this.yAxisScale = CreateYScale(chart);

            base.Init(chart);
        }

        internal abstract AxisScale<TChart, ValueAxisData<TXType, TXSpanType>, XAxisTick> CreateXScale(TChart chart);
        internal abstract AxisScale<TChart, ValueAxisData<TYType, TYSpanType>, YAxisTick> CreateYScale(TChart chart);

        private List<IDataType<TXType, TXSpanType>> cachedXMin = new List<IDataType<TXType, TXSpanType>>();
        private List<IDataType<TXType, TXSpanType>> cachedXMax = new List<IDataType<TXType, TXSpanType>>();
        private List<IDataType<TYType, TYSpanType>> cachedYMin = new List<IDataType<TYType, TYSpanType>>();
        private List<IDataType<TYType, TYSpanType>> cachedYMax = new List<IDataType<TYType, TYSpanType>>();

        private void CacheMinMax()
        {
            this.cachedXMin.Clear();
            this.cachedXMax.Clear();
            this.cachedYMin.Clear();
            this.cachedYMax.Clear();

            for (int i = 0; i < this.Chart.Series.Count; ++i)
            {
                this.cachedXMin.Add(this.Chart.Series[i].ActualXMinimum);
                this.cachedXMax.Add(this.Chart.Series[i].ActualXMaximum);
                this.cachedYMin.Add(this.Chart.Series[i].ActualYMinimum);
                this.cachedYMax.Add(this.Chart.Series[i].ActualYMaximum);
            }
        }

        internal override void HintSeriesChanged()
        {
            CacheMinMax();

            InvalidateAxes();

            base.HintSeriesChanged();
        }

        internal override void HintSeriesChanged(int series, int index)
        {
            bool dirty = false;

            for (int i = 0; i < this.Chart.Series.Count; ++i)
            {
                if (this.cachedXMin[i] == null || !this.cachedXMin[i].Equals(this.Chart.Series[i].ActualXMinimum) ||
                    this.cachedXMax[i] == null || !this.cachedXMax[i].Equals(this.Chart.Series[i].ActualXMaximum) ||
                    this.cachedYMin[i] == null || !this.cachedYMin[i].Equals(this.Chart.Series[i].ActualYMinimum) ||
                    this.cachedYMax[i] == null || !this.cachedYMax[i].Equals(this.Chart.Series[i].ActualYMaximum))
                {
                    CacheMinMax();
                    dirty = true;
                }
            }

            if (dirty)
            {
                InvalidateAxes();
            }

            base.HintSeriesChanged(series, index);
        }

        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
        {
            InvalidateAxes();

            base.OnRenderSizeChanged(sizeInfo);
        }

        protected void InvalidateAxes()
        {
            this.xAxisScale.InvalidateAxis();
            this.yAxisScale.InvalidateAxis();

            if (yInvalidate != null)
            {
                yInvalidate(this, new EventArgs());
            }

            if (xInvalidate != null)
            {
                xInvalidate(this, new EventArgs());
            }
        }

        protected void DrawBorder(DrawingContext dc, Pen pen)
        {
            RectangleGeometry geometry = new RectangleGeometry();
            geometry.Rect = new Rect(0, 0, this.ActualWidth, this.ActualHeight);
            dc.DrawGeometry(null, pen, geometry);
        }

        protected void DrawGrid(DrawingContext dc, Pen pen)
        {
            DrawYGrid(dc, pen);
            DrawXGrid(dc, pen);
        }

        private void DrawYGrid(DrawingContext dc, Pen pen)
        {
            for (int i = 0; i < this.YAxisScale.TickMarks.Count; ++i)
            {
                if (i == this.Chart.ActiveSeries)
                {
                    if (this.YAxisScale.TickMarks[i] != null)
                    {
                        for (int j = 0; j < this.YAxisScale.TickMarks[i].Count; ++j)
                        {
                            Point start = new Point(0, this.YAxisScale.TickMarks[i][j].Location);
                            Point end = new Point(this.ActualWidth, this.YAxisScale.TickMarks[i][j].Location);

                            dc.DrawLine(pen, start, end);
                        }
                    }
                }
            }
        }

        private void DrawXGrid(DrawingContext dc, Pen pen)
        {
            for (int i = 0; i < this.XAxisScale.TickMarks.Count; ++i)
            {
                if (i == this.Chart.ActiveSeries)
                {
                    if (this.XAxisScale.TickMarks[i] != null)
                    {
                        for (int j = 0; j < this.XAxisScale.TickMarks[i].Count; ++j)
                        {
                            Point start = new Point(this.XAxisScale.TickMarks[i][j].Location, 0);
                            Point end = new Point(this.XAxisScale.TickMarks[i][j].Location, this.ActualHeight);

                            dc.DrawLine(pen, start, end);
                        }
                    }
                }
            }
        }
    }
}